-
Notifications
You must be signed in to change notification settings - Fork 45
feat: Guide - Decoding JSON Web Tokens #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a new MDX documentation page at Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (8)
src/content/docs/build/tokens/decode-jwts.mdx (8)
30-30: Update the “updated” date to this PR’s timeline.Current: 2024-01-15. Suggest 2025-09-29 for accuracy.
-updated: 2024-01-15 +updated: 2025-09-29Please confirm the doc site’s date policy (authored vs updated).
114-125: Show explicit expected algorithms and domain/issuer guidance.Add recommended options and a note to pin expected algs (e.g., RS256) and issuer.
-const validationResult = await validateToken({ - token: "eyJhbGc...", - domain: "https://your-subdomain.kinde.com" -}); +const validationResult = await validateToken({ + token: "eyJhbGc...", + domain: "https://your-subdomain.kinde.com", + // Optional but recommended hardening: + expectedAlgs: ["RS256"], + expectedIssuer: "https://your-subdomain.kinde.com", +});I can add a short “why pin algorithms” callout if helpful.
166-191: Provide a complete TS implementation rather than a placeholder.Avoid “Implementation same as above”; include the base64url helper and typed return.
-interface DecodedJWT { +interface DecodedJWT { header: JWTHeader; payload: JWTPayload; signature: string; } -function decodeJWT(token: string): DecodedJWT { - // Implementation same as above -} +const b64urlToString = (b64url: string): string => { + const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/'); + const padded = b64 + '==='.slice((b64.length + 3) % 4); + // Browser vs Node + return typeof atob === 'function' + ? decodeURIComponent(escape(atob(padded))) + : Buffer.from(padded, 'base64').toString('utf8'); +}; + +export function decodeJWT(token: string): DecodedJWT { + const parts = token.split('.'); + if (parts.length !== 3) throw new Error('Invalid JWT format'); + const header = JSON.parse(b64urlToString(parts[0])) as JWTHeader; + const payload = JSON.parse(b64urlToString(parts[1])) as JWTPayload; + return { header, payload, signature: parts[2] }; +}
197-201: Tighten the security bullets with explicit “don’t trust until validated.”Clarify sequencing to prevent misinterpretation.
-**Decoding vs. Validation**: Decoding a JWT only extracts the payload - it doesn't verify the token's authenticity or integrity. -**Always Validate**: After decoding, always validate the token using proper cryptographic verification. +**Decoding vs. Validation**: Decoding only parses claims; it does not prove authenticity/integrity. +**Always Validate**: Perform cryptographic validation (signature + claims) and only then trust any decoded data.
204-212: Expand the validation checklist (alg, kid/JWKS, nbf/leeway, issuer, audience).Add commonly missed checks and adjust terminology.
-When validating JWTs, ensure you: +When validating JWTs, ensure you: - Verify the token signature using the public key - Check the `iss` (issuer) claim matches your Kinde domain - Validate the `aud` (audience) claim - Verify the `exp` (expiration) claim - Check the `iat` (issued at) claim is reasonable - Validate any custom claims specific to your application +- Enforce expected `alg` (e.g., RS256) and reject `none` or unexpected algorithms +- Use `kid` to select the correct key from JWKS and cache JWKS with rotation in mind +- Check `nbf` (not before) and allow small clock skew (e.g., ±60s leeway) +- For OIDC ID tokens, also verify `nonce` when applicableI can add a short JWKS caching callout if you want.
245-260: Harden feature‑flag access with nullish chaining and typing.Avoid runtime errors when claims are absent.
-function checkFeatureFlag(token, flagName) { +function checkFeatureFlag(token, flagName) { try { const decoded = jwtDecoder(token); - const featureFlags = decoded.payload.feature_flags; - - if (featureFlags && featureFlags[flagName]) { - return featureFlags[flagName].v; // 'v' is the value - } - - return false; + const v = decoded?.payload?.feature_flags?.[flagName]?.v; + return v ?? false; } catch (error) { console.error('Failed to check feature flag:', error); return false; } }
306-308: Rephrase “Validate Before Decoding.”You must parse to validate; intent is “don’t trust decoded contents until validated.”
-**Validate Before Decoding**: Always validate the token's signature and claims before trusting the decoded payload. +**Validate before trusting**: Parse, validate signature and claims, then trust the decoded payload.
68-75: Add explicitJWTDecodedimport and tighten the decoded payload exampleImport the exported
JWTDecodedtype and pass a concrete payload shape tojwtDecoder:-import { jwtDecoder } from "@kinde/jwt-decoder"; +import { jwtDecoder, type JWTDecoded } from "@kinde/jwt-decoder";Define a custom payload type and apply it:
-const decodedToken = jwtDecoder("eyJhbGc..."); +type MyClaims = JWTDecoded["payload"] & { + custom_claim?: string; + feature_flags?: Record<string, { v: unknown }>; +}; + +const decodedToken = jwtDecoder<MyClaims>("eyJhbGc...");Apply the same pattern in the example at lines 79–90.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/build/tokens/decode-jwts.mdx(1 hunks)
🔇 Additional comments (2)
src/content/docs/build/tokens/decode-jwts.mdx (2)
5-11: Inspect front-matter in decode-jwts.mdx
Please confirm presence and naming ofsdkorsdkskey in the front-matter once we have the file’s first lines.
112-112: Remove unused type import
jwtValidationResponseisn’t referenced in this file; drop it to avoid confusion.-import { validateToken, type jwtValidationResponse } from "@kinde/jwt-validator"; +import { validateToken } from "@kinde/jwt-validator";
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/content/docs/build/tokens/decode-jwts.mdx (1)
304-304: Minor style: Replace "only" with more varied language.To improve readability and flow, consider rephrasing "should only be used for display purposes" with an alternative structure.
Apply this diff for improved style:
-- **Never Trust Client-Side Decoding**: Client-side decoding should only be used for display purposes, not for security decisions. +- **Never Trust Client-Side Decoding**: Client-side decoding is intended for display purposes only and must not be used for security decisions.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/build/tokens/decode-jwts.mdx(1 hunks)
🧰 Additional context used
🪛 LanguageTool
src/content/docs/build/tokens/decode-jwts.mdx
[style] ~304-~304: Consider replacing ‘only’ with a different word to let your writing stand out.
Context: ...Decoding**: Client-side decoding should only be used for display purposes, not for s...
(ONLY_EXCLUSIVELY_STYLE)
🔇 Additional comments (2)
src/content/docs/build/tokens/decode-jwts.mdx (2)
3-4: ✓ Terminology corrected from previous review feedback.Title and keywords now properly use "Decoding and validating" terminology instead of "Decrypting," avoiding confusion between JWS (signed) and JWE (encrypted) tokens. This aligns with the previous review guidance.
Also applies to: 16-16, 24-24, 33-33
1-415: Documentation comprehensively covers JWT decoding patterns with strong security guidance.The document provides:
- Clear introduction distinguishing decoding from validation
- Two production-ready Kinde library options (Decoder & Validator) with installation and usage examples
- Three manual decoding implementations (browser, Node.js, universal) with proper error handling
- TypeScript types and interfaces for type-safe implementations
- Practical use cases (user info, feature flags, permissions extraction)
- Security considerations and validation checklist
- Error handling patterns and best practices
This is a solid foundation for developers learning JWT handling. The previous review's terminology and base64 decoding concerns have been substantially addressed.
Deploying kinde-docs-preview with
|
| Latest commit: |
a976789
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://5ae2cef2.kinde-docs-preview.pages.dev |
| Branch Preview URL: | https://feat-decodejwts.kinde-docs-preview.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/content/docs/build/tokens/decode-jwts.mdx (1)
311-311: Minor: Consider rewording to strengthen impact.The phrase "should only be used for display purposes" is clear, but you can strengthen it by restructuring to emphasize the security principle: "Client-side decoding is suitable only for display; do not make security decisions based on client-side decoded payloads."
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/build/tokens/decode-jwts.mdx(1 hunks)
🧰 Additional context used
🪛 LanguageTool
src/content/docs/build/tokens/decode-jwts.mdx
[style] ~311-~311: Consider replacing ‘only’ with a different word to let your writing stand out.
Context: ...Decoding**: Client-side decoding should only be used for display purposes, not for s...
(ONLY_EXCLUSIVELY_STYLE)
🔇 Additional comments (6)
src/content/docs/build/tokens/decode-jwts.mdx (6)
1-34: Front matter looks good.Terminology has been correctly aligned to "decoding" and "validating" (not "decrypting"), and metadata is well-structured.
36-43: Clear introduction with appropriate emphasis on JWS.The distinction between signed (JWS) and encrypted (JWE) tokens is clarified upfront, and the JWT structure explanation is well-presented.
44-121: Kinde libraries section is well-structured and practical.Installation instructions and usage examples for both decoder and validator are clear and easy to follow.
262-279: Align TypeScript base64UrlDecode encoding to match cross-platform version.The TypeScript implementation (line 273) uses
.toString('binary')while the cross-platform version (line 230) uses.toString('utf-8'). For consistency and clarity, both should use'utf-8', which is semantically correct since JWT payloads are UTF-8 encoded before base64url encoding.Apply this diff to align with the cross-platform approach:
function base64UrlDecode(str: string): string { // Replace Base64URL characters with Base64 characters let base64 = str.replace(/-/g, '+').replace(/_/g, '/'); // Add padding if needed (Base64 strings must be multiples of 4) while (base64.length % 4) { base64 += '='; } // Use Buffer in Node.js, atob in browser if (typeof globalThis.Buffer !== 'undefined') { - return globalThis.Buffer.from(base64, 'base64').toString('binary'); + return globalThis.Buffer.from(base64, 'base64').toString('utf-8'); } else if (typeof globalThis.atob !== 'undefined') { return globalThis.atob(base64); } else { throw new Error('Neither Buffer nor atob is available in this environment'); } }
305-324: Security section is comprehensive and appropriate.The validation checklist, security notes, and emphasis on validation before trusting the payload are well-articulated and important.
388-422: Error handling and best practices sections are solid.The examples demonstrate defensive programming, and the best practices provide clear, actionable guidance for developers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/content/docs/build/tokens/decode-jwts.mdx (1)
311-311: Minor style: Consider rewording to avoid "only."Line 311 states "Client-side decoding should only be used for display purposes." Per style guidance, this could be rephrased as "Client-side decoding is suitable for display purposes" or "Client-side decoding should be limited to display purposes" for variety, but the current phrasing is clear and acceptable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/build/tokens/decode-jwts.mdx(1 hunks)
🧰 Additional context used
🪛 LanguageTool
src/content/docs/build/tokens/decode-jwts.mdx
[style] ~311-~311: Consider replacing ‘only’ with a different word to let your writing stand out.
Context: ...Decoding**: Client-side decoding should only be used for display purposes, not for s...
(ONLY_EXCLUSIVELY_STYLE)
🔇 Additional comments (6)
src/content/docs/build/tokens/decode-jwts.mdx (6)
1-34: Front matter and metadata look solid.The documentation page is well-structured with comprehensive front matter, clear topic categorization, and appropriate audience/complexity settings. Content across the page correctly uses "decoding and validating" terminology and avoids earlier "decrypting" confusion.
44-121: Kinde library sections are clear and actionable.Installation instructions, basic usage, and validation workflows are well-documented with type-safe examples. Examples correctly reference the Kinde packages (@kinde/jwt-decoder, @kinde/jwt-validator) and show both synchronous and async patterns.
132-235: Cross-platform manual decoding implementations are correct.The browser (atob), Node.js (Buffer), and universal approaches all properly handle base64url padding and character replacement. The progression from environment-specific to universal is pedagogically sound for the audience.
262-279: TypeScript implementation properly handles cross-platform environments.The function correctly checks for Buffer availability via
globalThisand falls back toatobin browser contexts, with an error fallback for unexpected environments. Type annotations on interfaces and function parameters are accurate.
305-323: Security section appropriately emphasizes validation over decoding.The distinction between "decoding vs. validation" is clearly stated, and the validation checklist covers signature, issuer, audience, expiration, and custom claims—all critical for production use.
325-414: Use cases and error handling patterns provide practical guidance.Examples for displaying user info, feature flags, and permissions are realistic; error handling uses try-catch with graceful fallbacks. The
safeDecodeJWThelper properly validates token structure before decoding.
|
Hi @Arobce thanks a lot for checking the code for accuracy. Would you be able to check the correctness of the 3 functions used for manual decoding? |
|
@tamalchowdhury did that along with my other tests. Its working perfectly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/content/docs/build/tokens/decode-jwts.mdx (1)
402-420: Minor: Rephrase to avoid overuse of "only" (optional refactor).Line 408 uses "only" in a way that can be strengthened for clarity. Consider:
- - **Never Trust Client-Side Decoding**: Client-side decoding should only be used for display purposes, not for security decisions. + - **Never Trust Client-Side Decoding**: Use client-side decoding exclusively for display purposes; never rely on it for security decisions.The security checklist and best practices sections are otherwise comprehensive and well-structured.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/build/tokens/decode-jwts.mdx(1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.29.1)
src/content/docs/build/tokens/decode-jwts.mdx
[high] 69-69: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🪛 LanguageTool
src/content/docs/build/tokens/decode-jwts.mdx
[style] ~408-~408: Consider replacing ‘only’ with a different word to let your writing stand out.
Context: ...Decoding**: Client-side decoding should only be used for display purposes, not for s...
(ONLY_EXCLUSIVELY_STYLE)
🔇 Additional comments (5)
src/content/docs/build/tokens/decode-jwts.mdx (5)
1-42: Terminology and structure are correct.Front matter and introduction properly use "decoding/validation" terminology and accurately describe JWT structure. Prior terminology feedback has been appropriately addressed.
44-90: jwtDecoder behavior correctly documented.The example output (line 75) correctly shows the payload only, not header and signature. This accurately reflects Kinde's jwtDecoder implementation per the PR feedback.
92-141: Validation and decoding flow is correct.The example properly separates concerns: validateToken returns
{ valid, message }(not payload), flow checksresult.validbefore calling jwtDecoder, and error handling wraps with try/catch. This implements the recommended pattern from PR feedback.
143-323: Manual decoding implementations are cross-platform and correct.Browser, Node.js, universal, and TypeScript implementations all properly handle environment detection and base64url decoding with padding. TypeScript version correctly uses
globalThis.Bufferwith browser fallback. Prior feedback on cross-platform support has been addressed.
69-69: Gitleaks flag is expected for documentation examples.The example JWT on line 69 is a standard token used to demonstrate jwtDecoder output in documentation. This is not a real secret or credential and does not represent a security risk in this context. The alert is a false positive for documentation examples.
| ### Displaying User Information | ||
|
|
||
| You can extract user information from decoded tokens, including email, organization code, feature flags, and permissions. | ||
|
|
||
| By default, the email claim is not included in the `access_token`. To enable it: | ||
|
|
||
| 1. Go to **Application** > **View Details** > **Tokens** > **Access Token** and select **Customize**. | ||
| 2. Enable the **Email (string)** claim. | ||
| 3. Select **Save**. | ||
|
|
||
| If you need to access the user's full name and profile picture, use the `id_token` instead of the access token. The `id_token` includes these claims by default. You can decode the `id_token` using the same method as the access token. Learn more about [ID tokens](/build/tokens/about-id-tokens/). | ||
|
|
||
| ```javascript | ||
| import { jwtDecoder } from "@kinde/jwt-decoder" | ||
|
|
||
| function displayUserInfo(token) { | ||
| try { | ||
| const payload = jwtDecoder(token) | ||
|
|
||
| // Note: Email must be enabled in token customization for access tokens | ||
| console.log(`User: ${payload.email}`) | ||
| console.log(`Organization code: ${payload.org_code}`) | ||
| console.log(`Permissions: ${payload.permissions?.join(", ")}`) | ||
|
|
||
| return { | ||
| email: payload?.email || "", | ||
| org_code: payload.org_code, | ||
| permissions: payload.permissions || [], | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to decode token:", error) | ||
| return null | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add disclaimers for org_code and permissions attributes.
The "Displaying User Information" section documents that email requires dashboard enablement (lines 331–335), but similar disclaimers are missing for org_code and permissions attributes accessed on lines 348–349. Per PR feedback, attributes not available by default should be documented as requiring Kinde dashboard enablement.
Add a note clarifying which attributes are optional or require configuration:
If you need to access the user's full name and profile picture, use the `id_token` instead of the access token. The `id_token` includes these claims by default. You can decode the `id_token` using the same method as the access token. Learn more about [ID tokens](/build/tokens/about-id-tokens/).
+**Note:** Additional attributes like `org_code` and `permissions` may require configuration in your Kinde application. Check [token customization documentation](#) to enable these claims.
+
```javascript🤖 Prompt for AI Agents
In src/content/docs/build/tokens/decode-jwts.mdx around lines 327 to 361, add
brief disclaimers stating that org_code and permissions are not included by
default and must be enabled/configured in the Kinde dashboard (similar to the
email note): update the explanatory text above the code block and adjust the
comments inside the example to mention that org_code and permissions require
dashboard customization and may be undefined, and change the returned object/log
lines to defensively handle missing org_code and permissions (e.g., default to
empty string/array).
| ### Checking Feature Flags | ||
|
|
||
| ```javascript | ||
| import { jwtDecoder } from "@kinde/jwt-decoder" | ||
|
|
||
| function checkFeatureFlag(token, flagName) { | ||
| try { | ||
| const payload = jwtDecoder(token); | ||
|
|
||
| const featureFlags = payload.feature_flags; | ||
|
|
||
| if (featureFlags && featureFlags[flagName]) { | ||
| return featureFlags[flagName].v; | ||
| } | ||
|
|
||
| return false; | ||
| } catch (error) { | ||
| console.error('Failed to check feature flag:', error); | ||
| return false; | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add disclaimer for feature_flags attribute.
The "Checking Feature Flags" example accesses payload.feature_flags without documenting that this attribute requires enablement in the Kinde dashboard. Per PR feedback, optional attributes should be documented as needing configuration.
Add a note before or within the code example:
### Checking Feature Flags
+**Note:** Feature flags must be enabled in your Kinde application for the `feature_flags` attribute to be present in the token payload.
+
```javascript🤖 Prompt for AI Agents
In src/content/docs/build/tokens/decode-jwts.mdx around lines 363 to 384, the
example accesses payload.feature_flags without documenting that this attribute
is optional and must be enabled in the Kinde dashboard; add a brief disclaimer
immediately above the "Checking Feature Flags" heading or directly before the
code block stating that feature_flags is an optional attribute that must be
enabled/configured in the Kinde dashboard (and will be absent otherwise), so
readers know to enable it before using the example; keep the note short and
place it as plain text (one or two sentences) before the code block.
| ### Extracting Permissions | ||
|
|
||
| ```javascript | ||
| import { jwtDecoder } from "@kinde/jwt-decoder" | ||
|
|
||
| export function getUserPermissions(token) { | ||
| try { | ||
| const payload = jwtDecoder(token) | ||
| return payload.permissions || [] | ||
| } catch (error) { | ||
| console.error('Failed to extract permissions:', error) | ||
| return [] | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add disclaimer for permissions attribute.
The "Extracting Permissions" example accesses payload.permissions without documenting that it may require enablement. Consistent with the email disclaimer in "Displaying User Information," add a note that permissions must be configured in the Kinde dashboard.
### Extracting Permissions
+**Note:** Permissions are included in the token payload by default when users have role-based permissions configured in Kinde. Ensure roles and permissions are set up in your Kinde application.
+
```javascript🤖 Prompt for AI Agents
In src/content/docs/build/tokens/decode-jwts.mdx around lines 386 to 400, the
example reads payload.permissions but lacks the same disclaimer used for
displayed user info; add a short note directly above or below the "Extracting
Permissions" code block stating that the permissions attribute must be
enabled/configured in the Kinde dashboard (or via org settings) before it will
appear in the token, mirroring the wording/style used in the "Displaying User
Information" email disclaimer; keep the note concise and clearly linked to the
example so readers know to enable permissions in the dashboard if they receive
an empty array.
|
@Arobce would you be willing to have another look at the overall structure of this guide? thanks. |


New topic about JWT decoding, referencing the Kinde utils we have to do this.
Needs review by @DanielRivers at minimum.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.